package com.lm.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.bean.ProducerBean;
import com.kwd.rocketmq.RocketmqEvent;
import com.lm.dao.KwChannelDataDao;
import com.lm.dao.LmProductCashDao;
import com.lm.dao.LmTrackEventDao;
import com.lm.dao.LmTrackEventInfoDao;
import com.lm.model.KwChannel;
import com.lm.model.LmProductCash;
import com.lm.model.LmTrackEvent;
import com.lm.model.LmTrackEventInfo;
import com.lm.service.KwChannelService;
import com.lm.service.LmProductCashService;
import com.lm.service.LmTrackEventService;
import com.meirengu.common.RedisClient;
import com.meirengu.service.impl.BaseServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.meirengu.utils.DateUtils.getDayBeginTime;
import static com.meirengu.utils.DateUtils.getDayEndTime;
import static java.util.Calendar.*;
import static org.apache.commons.lang3.StringUtils.*;

/**
 * LmTrackEvent服务实现层
 *
 * @author Mao
 * @create Sat Jun 09 15:33:36 CST 2018
 */
@Service
public class LmTrackEventServiceImpl extends BaseServiceImpl<LmTrackEvent> implements LmTrackEventService {

    private static final Logger logger = LoggerFactory.getLogger(LmTrackEventServiceImpl.class);

    @Autowired
    LmTrackEventDao lmTrackEventDao;

    @Autowired
    LmTrackEventInfoDao lmTrackEventInfoDao;

    @Autowired
    private LmProductCashDao lmProductCashDao;

    @Autowired
    private KwChannelService kwChannelService;

    @Autowired
    private KwChannelDataDao kwChannelDataDao;

    @Autowired
    private LmProductCashService lmProductCashService;

    @Value("${producter.topic.kwd.product}")
    private String topic;

    @Autowired
    ProducerBean producer;

    @Autowired
    RedisClient redisClient;

    private final static String PREFIX_COUNT_CLICK = "count_click_";
    private final static String PREFIX_COUNT_IP = "count_ip_";
    private final static String PREFIX_COUNT_SESSION = "count_session_";
    private final static String PREFIX_COUNT_JSESSION = "count_jsession_id_";
    private final String PREFIX_COUNT_CHANNEL_DATA_UV = "count_channel_data_uv_";

    /**
     * 事件跟踪
     * 1.先记录原始记录
     * 2.更新渠道的产品点击量
     * 3.查询当日记录，更新
     * 4.判断渠道是否下架
     * 每个逻辑独立，互不影响
     *
     * @param id
     * @throws Exception
     */
    public void trackEvent(Integer id, String ip, Integer userId, String userName, String cid, String mobile, String uuid) throws Exception {

        /*校验参数*/
        if (isBlank(cid)) {
            cid = "app";
        }
        if (isNotBlank(cid) && cid.contains("#")) {
            int endIndex = cid.indexOf("#");
            cid = cid.substring(0, endIndex);
        }
        /*1.先记录原始记录*/
        LmProductCash productCash = lmProductCashDao.detail(id);
        if (productCash != null) {
            LmTrackEventInfo lmTrackEventInfo = new LmTrackEventInfo();
            lmTrackEventInfo.setUserId(userId);
            lmTrackEventInfo.setUserName(userName);
            lmTrackEventInfo.setProductId(id);
            lmTrackEventInfo.setAction("跳转");
            lmTrackEventInfo.setUserMobile(mobile);
            lmTrackEventInfo.setCid(cid);
            lmTrackEventInfo.setProductName(productCash.getProductName());
            lmTrackEventInfo.setIp(ip);
            lmTrackEventInfo.setSessionId(uuid);
            lmTrackEventInfoDao.insert(lmTrackEventInfo);
        }
        /*2.更新渠道的产品点击量*/
        KwChannel kwChannel = kwChannelService.getByCode(cid);
        // 查出用户渠道并统计到渠道数据表中
        if (kwChannel != null && isNotBlank(kwChannel.getId())) {
            Map<String, String> paramsMap = new HashMap<>();
            paramsMap.put("channelId", kwChannel.getId());
            paramsMap.put("productNum", "1");
            kwChannelDataDao.addNum(paramsMap);
        }
        /*3.查询当日记录，更新*/
        Date currentDate = new Date();
        //当天开始时间
        Date dataDateBegin = getDayBeginTime(currentDate);
        //当天结束时间
        Date dataDateEnd = getDayEndTime(currentDate);
        Map<String, Object> map = new HashMap<>();
        map.put("productId", id);
        map.put("dataDateBegin", dataDateBegin);
        map.put("dataDateEnd", dataDateEnd);

        List<Map<String, Object>> trackEventList = getList(map);
        if (trackEventList != null && trackEventList.size() > 0) {
            Map<String, Object> eventMap = trackEventList.get(0);
            if (isNotBlank(eventMap.get("id").toString())) {
                lmTrackEventDao.updatePv(eventMap.get("id").toString());
            }
            /*4.判断渠道是否下架*/
            if ("Y".equals(productCash.getLimitPreset())) {
                Integer trackNum = (Integer) eventMap.get("pv");
                Integer presetNum = (Integer) eventMap.get("presetNum");
                if (trackNum + 1 >= presetNum) {
                    LmProductCash lmProductCash1 = new LmProductCash();
                    lmProductCash1.setId(id);
                    lmProductCashService.updateStatus(lmProductCash1);
                }
            }
            /**产品详情已查询，没必要使用缓存判断*/
            /*String limitPreset = redisClient.hget("product_preset", "limit_preset_" + id);
            if ("Y".equals(limitPreset)) {
                String outLimit = redisClient.hget("product_preset", "out_preset_" + id);
                if ("Y".equals(outLimit)) {
                    LmProductCash lmProductCash = new LmProductCash();
                    lmProductCash.setId(id);
                    lmProductCashDao.updateStatus(lmProductCash);
                    redisClient.hset("product_preset", "out_preset_" + id, "Y");
                } else {
                    Integer trackNum = (Integer) eventMap.get("pv");
                    Integer presetNum = (Integer) eventMap.get("presetNum");
                    if (trackNum + 1 >= presetNum) {
                        LmProductCash lmProductCash = new LmProductCash();
                        lmProductCash.setId(id);
                        lmProductCashService.updateStatus(lmProductCash);
                        redisClient.hset("product_preset", "out_preset_" + id, "Y");
                    }
                }
            } else if (limitPreset == null) {
                //redis缓存中无限制信息
                if ("Y".equals(productCash.getLimitPreset())) {
                    Integer trackNum = (Integer) eventMap.get("pv");
                    Integer presetNum = (Integer) eventMap.get("presetNum");
                    if (trackNum + 1 >= presetNum) {
                        LmProductCash lmProductCash1 = new LmProductCash();
                        lmProductCash1.setId(id);
                        lmProductCashService.updateStatus(lmProductCash1);
                    }
                }
            }*/
        }

    }

    /**
     * 用户在贷款超市的前端界面上点击了一个第三方产品的处理过程
     * 1.先记录原始记录
     * 2.更新渠道的产品点击量
     * 3.查询当日记录，更新
     * 4.判断渠道是否下架
     * 每个逻辑独立，互不影响
     *
     * @param cid 前端界面的渠道id
     */
    @Override
    public void trackEvent(Integer id, String ip, Integer userId, String userName, String cid, String mobile, String uuid
            , String jsessionId) throws Exception {
        if (isBlank(cid)) {
            cid = "app";
        } else if (cid.contains("#")) {
            int i = cid.indexOf('#');
            cid = cid.substring(0, i);
        }

        //1.先记录原始记录
        LmProductCash productCash = insertTrackEventInfo(id, ip, userId, userName, cid, mobile, uuid, jsessionId);

        //传入的id要有效，防止恶意刷接口
        if (productCash != null) {
            //2.使用mq更新渠道的产品点击量
            Message msg1 = new Message();
            msg1.setTopic(topic);
            msg1.setTag("update_channel_product_num");

            JSONObject jsonObject1 = new JSONObject();
            jsonObject1.put("cid", cid);
            jsonObject1.put("productId", id);
            jsonObject1.put("uuid", uuid);
            msg1.setBody(jsonObject1.toJSONString().getBytes(Charset.forName("UTF-8")));
            producer.send(msg1);

            //3.mq查询当日记录，更新
            Message msg2 = new Message();
            msg2.setTopic(topic);
            msg2.setTag("update_track_event_pv");

            JSONObject jsonObject = new JSONObject();
            jsonObject.put("productId", id);
            jsonObject.put("ip", ip);
            jsonObject.put("sessionId", uuid);
            jsonObject.put("jsessionId", jsessionId);
            jsonObject.put("limitPreset", productCash.getLimitPreset());
            jsonObject.put("totalUv", productCash.getTotalUv());
            msg2.setBody(jsonObject.toJSONString().getBytes());
            producer.send(msg2);
        }
    }

    @EventListener(condition = "#event.tag=='update_track_event_pv'")
    public void updateTrackEventPv(RocketmqEvent event) {
        JSONObject jsonObject = JSON.parseObject(event.getMsg());

        Integer productId = jsonObject.getInteger("productId");
        String limitPreset = jsonObject.getString("limitPreset");

        String ip = jsonObject.getString("ip");
        String sessionId = jsonObject.getString("sessionId");
        String jsessionId = jsonObject.getString("jsessionId");

        Integer totalUv = jsonObject.getInteger("totalUv");

        if (productId != null && productId > 0) {
            Date currentDate = new Date();
            //当天开始时间
            Date dataDateBegin = getDayBeginTime(currentDate);
            //当天结束时间
            Date dataDateEnd = getDayEndTime(currentDate);
            Map<String, Object> map = new HashMap<>();
            map.put("productId", productId);
            map.put("dataDateBegin", dataDateBegin);
            map.put("dataDateEnd", dataDateEnd);

            List<Map<String, Object>> trackEventList = getList(map);
            if (trackEventList != null && trackEventList.size() > 0) {
                Map<String, Object> eventMap = trackEventList.get(0);

                //是否需要更新totalUv字段
                boolean updateTotalUv = false;

                if (isNotBlank(eventMap.get("id").toString())) {
                    //判断sessionId是不是新值
                    boolean sessionExists = sessionIdExists(productId, totalUv, sessionId);
                    logger.info("sessionId exists:{}", sessionExists);

                    addOnRedis(productId, ip, sessionId, jsessionId);

                    Long countClick = Long.parseLong(redisClient.get(PREFIX_COUNT_CLICK + productId));

                    Integer countClickDb = (Integer) eventMap.get("countClick");

                    if (countClickDb == null) {
                        countClickDb = 0;
                    }

                    //使用Redis中统计的点击数和数据库中统计的点击数对比，Redis中的数一定比数据库中的数大
                    if (countClick > countClickDb) {
                        Map<String, Object> params = new HashMap<>();
                        params.put("id", eventMap.get("id"));
                        params.put("countClick", countClick);
                        params.put("countIP", redisClient.scard(PREFIX_COUNT_IP + productId));
                        params.put("countSessionId", redisClient.scard(PREFIX_COUNT_SESSION + productId));
                        params.put("countJsessionId", redisClient.scard(PREFIX_COUNT_JSESSION + productId));

                        lmTrackEventDao.updateTrackEventCount(params);
                    } else {
                        int id = (Integer) eventMap.get("id");
                        Date dataDate = (Date) eventMap.get("dataDate");
                        updateTrackEventPvByDate(id, productId, dataDate);
                    }

                    //新sessionId，且有总UV数
                    updateTotalUv = !sessionExists && (totalUv != null && totalUv > 0);
                    if (updateTotalUv) {
                        lmProductCashService.updateTotalUv(productId);
                    }

                }
                //4.判断渠道是否下架
                checkLimit(limitPreset, productId, eventMap, updateTotalUv, totalUv);
            }
        }
    }

    /**
     * 判断sessionId是否已经存在
     */
    private boolean sessionIdExists(Integer productId, Integer totalUv, String sessionId) {
        logger.info("sessionIdExists params;productId:{},totalUv:{},sessionId:{}", productId, totalUv, sessionId);
        //已设置有效的总uv数
        if (totalUv != null && totalUv > 0) {
            if (!isEmpty(sessionId)) {
                String key = PREFIX_COUNT_SESSION + productId;
                if (redisClient.exists(key)) {
                    return redisClient.sismember(key, sessionId);
                } else {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 判断点击数是否达到
     */
    private void checkLimit(String limitPreset, Integer productId, Map<String, Object> eventMap, boolean updateTotalUv, Integer totalUv) {
        if (StringUtils.equals("Y", limitPreset)) {
            boolean disableProduct = false;
            //检测pv数达到上限
            Integer pv = (Integer) eventMap.get("pv");
            Integer presetNum = (Integer) eventMap.get("presetNum");

            //presetNum可能为null
            if (presetNum != null && presetNum > 0) {
                if (pv + 1 >= presetNum) {
                    disableProduct = true;
                    logger.info("pv disableProduct:{}", disableProduct);
                }
            }

            //检测uv数达到上限
            Integer uv = (Integer) eventMap.get("countSessionId");
            Integer presetUv = (Integer) eventMap.get("presetUv");
            //presetUv可能为null
            if (presetUv != null && presetUv > 0) {
                if (uv + 1 >= presetUv) {
                    disableProduct = true;
                    logger.info("uv uv:{},presetUv:{},disableProduct:{}", uv + 1, presetUv, disableProduct);
                }
            }

            //检查是否达到总uv点击数
            if (updateTotalUv && (totalUv != null && (totalUv - 1) <= 0)) {
                disableProduct = true;
                logger.info("totalUv disableProduct:{}", disableProduct);
            }

            if (disableProduct) {
                LmProductCash lmProductCash1 = new LmProductCash();
                lmProductCash1.setId(productId);
                lmProductCashService.updateStatus(lmProductCash1);
            }
        }
    }

    /**
     * 修改指定日期的track_event的数据
     */
    private void updateTrackEventPvByDate(int id, int productId, Date dataDate) {
        Map<String, Object> params = new HashMap<>();

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        params.put("startDate", simpleDateFormat.format(dataDate));

        Calendar calendar = getInstance();
        calendar.setTime(dataDate);
        calendar.add(DAY_OF_MONTH, 1);
        String endDate = simpleDateFormat.format(calendar.getTime());

        params.put("endDate", endDate);

        params.put("productId", productId);

        Map<String, Object> countMap = lmTrackEventInfoDao.countItems(params);

        params.clear();
        params.put("id", id);
        params.put("countClick", countMap.get("countClick"));
        params.put("countIP", countMap.get("countIp"));
        params.put("countSessionId", countMap.get("countSessionId"));
        params.put("countJsessionId", countMap.get("countJsessionId"));

        lmTrackEventDao.updateTrackEventCount(params);
    }

    /**
     * 使用Redis来统计这些参数，按产品（productId）为单位来统计
     */
    private void addOnRedis(Integer productId, String ip, String sessionId, String jsessionId) {
        long expireTime = getExpireTime();

        String keyCountClick = PREFIX_COUNT_CLICK + productId;

        if (!redisClient.exists(keyCountClick)) {
            redisClient.set(keyCountClick, "1");
            redisClient.expireAt(keyCountClick, expireTime);
        } else {
            redisClient.incr(keyCountClick);
        }

        addOnSet(ip, PREFIX_COUNT_IP + productId, expireTime);
        addOnSet(sessionId, PREFIX_COUNT_SESSION + productId, expireTime);
        addOnSet(jsessionId, PREFIX_COUNT_JSESSION + productId, expireTime);
    }

    /**
     * 获取Redis中key的过期时间，明日的0点
     *
     * @return 距1970.1.1，00:00:00.000的秒数
     */
    private long getExpireTime() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(DAY_OF_MONTH, 1);
        calendar.set(HOUR_OF_DAY, 0);
        calendar.set(MINUTE, 0);
        calendar.set(SECOND, 0);
        calendar.set(MILLISECOND, 0);

        return calendar.getTimeInMillis() / 1000;
    }

    /**
     * 向Redis的Set集合中添加数据，并设置过期时间
     */
    private void addOnSet(String value, String key, long expireTime) {
        if (!isEmpty(value)) {
            if (!redisClient.exists(key)) {
                redisClient.sadd(key, value);
                redisClient.expireAt(key, expireTime);
            } else {
                redisClient.sadd(key, value);
            }
        }
    }

    /**
     * 统计渠道的点击数，UV数
     */
    @EventListener(condition = "#event.tag=='update_channel_product_num'")
    public void updateChannelProductNum(RocketmqEvent event) {
        JSONObject jsonObject = JSONObject.parseObject(event.getMsg());
        String cid = jsonObject.getString("cid");
        Integer productId = jsonObject.getInteger("productId");
        String uuid = jsonObject.getString("uuid");

        if (!isBlank(cid) && (productId != null && productId > 0) && !isBlank(uuid)) {
            KwChannel kwChannel = kwChannelService.getByCode(cid);
            // 查出用户渠道并统计到渠道数据表中
            if (kwChannel != null && isNotBlank(kwChannel.getId())) {
                String key = PREFIX_COUNT_CHANNEL_DATA_UV + cid;

                if (redisClient.exists(key)) {
                    redisClient.sadd(key, productId + "_" + uuid);
                } else {
                    redisClient.sadd(key, productId + "_" + uuid);
                    long expireTime = getExpireTime();
                    redisClient.expireAt(key, expireTime);
                }

                Long times = redisClient.scard(key);

                Map<String, Object> paramsMap = new HashMap<>();
                paramsMap.put("channelId", kwChannel.getId());
                paramsMap.put("productNum", "1");
                paramsMap.put("uv", times);
                kwChannelDataDao.addNum(paramsMap);
            }
        }
    }

    /**
     * 插入原始的访问记录
     */
    private LmProductCash insertTrackEventInfo(Integer id, String ip, Integer userId, String userName, String cid
            , String mobile, String uuid, String jsessionId) {
        LmProductCash productCash = lmProductCashDao.detail(id);
        if (productCash != null) {
            LmTrackEventInfo lmTrackEventInfo = new LmTrackEventInfo();
            lmTrackEventInfo.setUserId(userId);
            lmTrackEventInfo.setUserName(userName);
            lmTrackEventInfo.setProductId(id);
            lmTrackEventInfo.setAction("跳转");
            lmTrackEventInfo.setUserMobile(mobile);
            lmTrackEventInfo.setCid(cid);
            lmTrackEventInfo.setProductName(productCash.getProductName());
            lmTrackEventInfo.setIp(ip);
            lmTrackEventInfo.setSessionId(uuid);
            lmTrackEventInfo.setJsessionId(jsessionId);
            lmTrackEventInfoDao.insert(lmTrackEventInfo);
        }
        return productCash;
    }

}
